package com.ecoolex.ying.space.core.service;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.aliyun.opensearch.DocumentClient;
import com.aliyun.opensearch.OpenSearchClient;
import com.aliyun.opensearch.SearcherClient;
import com.aliyun.opensearch.sdk.generated.OpenSearch;
import com.aliyun.opensearch.sdk.generated.commons.OpenSearchClientException;
import com.aliyun.opensearch.sdk.generated.commons.OpenSearchException;
import com.aliyun.opensearch.sdk.generated.commons.OpenSearchResult;
import com.aliyun.opensearch.sdk.generated.search.*;
import com.aliyun.opensearch.sdk.generated.search.general.SearchResult;
import com.aliyun.opensearch.search.SearchParamsBuilder;
import com.aliyun.opensearch.search.SearchResultDebug;
import com.ecoolex.framework.common.exception.RunningException;
import com.ecoolex.framework.common.pojo.Page;
import com.ecoolex.framework.common.pojo.Pagination;
import com.ecoolex.framework.common.util.Check;
import com.ecoolex.framework.common.util.RunTimingUtil;
import com.ecoolex.framework.common.util.StringUtil;
import com.ecoolex.ying.space.core.manager.PickGoodsLabelAssociateManager;
import com.ecoolex.ying.space.core.manager.PickGoodsManager;
import com.ecoolex.ying.space.core.manager.SearchPickGoodsManager;
import com.ecoolex.ying.space.entity.*;
import com.ecoolex.ying.space.enums.PickGoodsStatus;
import com.ecoolex.ying.space.pojo.EditGoodsDTO;
import com.ecoolex.ying.space.pojo.SearchPickGoodsDTO;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;

@Component
public class PickGoodsSearchService {

    private Logger log = LoggerFactory.getLogger(this.getClass());

    @Value("${aliyun.opensearch.access.id}")
    private String accesskey;

    @Value("${aliyun.opensearch.access.secret}")
    private String secret;

    @Value("${aliyun.opensearch.host}")
    private String host;

    @Value("${aliyun.opensearch.pickgoods.appname}")
    private String appName;

    private OpenSearchClient serviceClient;

    @PostConstruct
    private void initOpenSearch() {
        OpenSearch openSearch = new OpenSearch(accesskey, secret, host);
        //创建OpenSearchClient对象，并以OpenSearch对象作为构造参数
        serviceClient = new OpenSearchClient(openSearch);
    }

    @Autowired
    private SearchPickGoodsManager searchPickGoodsManager;

    @Autowired
    private PickGoodsManager pickGoodsManager;

    @Autowired
    private PickGoodsLabelAssociateManager pickGoodsLabelAssociateManager;

    public Pagination<PickGoodsWithBLOBs> search(SearchPickGoodsDTO query, Page page) {
        return RunTimingUtil.timing(log,
                log.isDebugEnabled(),
                () -> RunningException.wrap(() -> search2(query, page), "搜索优选商品服务异常"),
                "搜索优选商品服务");
    }

    private String buildQueryString(SearchPickGoodsDTO query) {
        // 关键字
        if (Check.notNullOrTrimEmpty(query.getKeyword())) {
            StringBuffer sb = new StringBuffer();
            List<String> orKeyword = Lists.newArrayList();
            String[] strArr = query.getKeyword().split("\\s+");
            for (String keyword : strArr) {
                if (Check.notTrimEmpty(keyword)) {
                    orKeyword.add("default:'" + StringUtil.trim(keyword) + "'");
                }
            }
            sb.append(StringUtil.joint(orKeyword, " OR "));
            return sb.toString();
        } else {
            return null;
        }
    }

    private Pagination<PickGoodsWithBLOBs> search2(SearchPickGoodsDTO query, Page page) throws OpenSearchClientException, OpenSearchException {
        log.debug("开始搜索优选商品: {}, {}", query, page);
        //定义Config对象，用于设定config子句参数，分页或数据返回格式，指定应用名等等
        Config config = new Config(Lists.newArrayList(appName));
        config.setStart(page.getFirstIndex());
        config.setHits(page.getPageSize());
        //设置返回格式为FULLJSON，目前支持返回 XML，JSON，FULLJSON 等格式
        config.setSearchFormat(SearchFormat.FULLJSON);
        // 设置搜索结果返回应用中哪些字段
        config.setFetchFields(Lists.newArrayList("pickgoodsid"));
        // 注意：config子句中的rerank_size参数，在Rank类对象中设置
        //设置Kvpairs子句参数,此处为distinct子句添加uniq插件，用于reserverd=false时，total及viewtotal不准确问题
        config.setKvpairs("duniqfield:pickgoodsid");

        // 设置聚合打散子句
        Distinct dist = new Distinct();
        dist.setKey("pickgoodsid"); //设置dist_key
        dist.setDistCount(1); //设置dist_count
        dist.setDistTimes(1); //设置dist_times
        dist.setReserved(false); //设置reserved
        dist.setUpdateTotalHit(true); //设置update_total_hit
        //此处duniqfield参数通过在config子句中指定kvpairs子句形式添加

        // 设置查询过滤条件
        // searchParams.setFilter("id > \"0\""); //此处也可改用后面的ParamsBuilder实现添加过滤条件
        // 设置sort条件
//        Sort sorter = new Sort();
//        sorter.addToSortFields(new SortField("worksid", Order.DECREASE)); //设置id字段降序
//        //添加Sort对象参数
//        searchParams.setSort(sorter);
        // 设置粗精排表达式，此处设置为默认
        Rank rank = new Rank();
        rank.setFirstRankName("default");
        rank.setSecondRankName("default");
        rank.setReRankSize(page.getPageSize());//设置参与精排文档个数

        // 创建参数对象
        SearchParams searchParams = new SearchParams(config);
        // 设置查询子句，若需多个索引组合查询，需要setQuery处合并，否则若设置多个setQuery后面的会替换前面查询
        searchParams.setQuery(buildQueryString(query));
        //添加Distinct对象参数
        searchParams.addToDistincts(dist);
        // 设置粗精排表达式，此处设置为默认
        searchParams.setRank(rank);

        SearchParamsBuilder paramsBuilder = SearchParamsBuilder.create(searchParams);
        // 设置查询过滤条件
//        paramsBuilder.addFilter("worksid>=0", "AND");
//        paramsBuilder.addFilter("deleted=0", "AND");
        if (Check.notNull(query.getParentClassifyId())) {
            paramsBuilder.addFilter("parentclassifyid=" + query.getParentClassifyId());
        }
        if (Check.notNull(query.getClassifyId())) {
            paramsBuilder.addFilter("classifyid=" + query.getClassifyId());
        }
        if (Check.notNull(query.getBrandId())) {
            paramsBuilder.addFilter("brandid=" + query.getBrandId());
        }
        if (Check.notNull(query.getLabelId())) {
            paramsBuilder.addFilter("labelidlist=" + query.getLabelId());
        }
        if (Check.notNull(query.getSource())) {
            paramsBuilder.addFilter("source=" + query.getSource());
        }
        paramsBuilder.addFilter("status=" + PickGoodsStatus.ONLINE.real());

        //创建SearcherClient对象，并以OpenSearchClient对象作为构造参数
        SearcherClient client = new SearcherClient(serviceClient);
        SearchResult searchResult = client.execute(paramsBuilder);
        log.debug("搜索优选商品结果: {}", searchResult);
        if (log.isDebugEnabled()) {
            //输出上次查询请求串信息
            SearchResultDebug searchdebugrst = client.executeDebug(searchParams);
            log.debug("搜索优选商品查询数据: {}", JSON.toJSONString(searchdebugrst));
        }

        JSONObject resultJson = JSON.parseObject(searchResult.getResult());

        JSONObject result = resultJson.getJSONObject("result");
        Integer total = result.getInteger("total");

        List<Integer> pickGoodsIdList = result.getJSONArray("items")
                .stream()
                .map(JSONObject.class::cast)
                .map(obj -> obj.getJSONObject("fields").getInteger("pickgoodsid"))
                .collect(Collectors.toList());

        log.debug("搜索优选商品结果集={}", pickGoodsIdList);

        return Pagination.create(page, total, () -> {
            // 还原结果顺序
            Map<Integer, PickGoodsWithBLOBs> mapResult = pickGoodsManager.queryByPickGoodsIds(pickGoodsIdList).stream()
                    .collect(Collectors.toMap(PickGoodsWithBLOBs::getPickGoodsId, self -> self));
            return pickGoodsIdList.stream()
                    .map(pickGoodsId -> mapResult.get(pickGoodsId))
                    .collect(Collectors.toList());
        });
    }

    public void create(Integer pickGoodsId) {
        // 创建索引
        log.debug("创建优选商品索引={}", pickGoodsId);
        commit(client -> client.add(build(pickGoodsId)));
    }

    public void update(Integer pickGoodsId) {
        // 更新索引
        log.debug("更新优选商品索引={}", pickGoodsId);
        commit(client -> client.update(build(pickGoodsId)));
    }

    public void delete(Integer pickGoodsId) {
        // 删除索引
        log.debug("删除优选商品索引={}", pickGoodsId);
        commit(client -> {
            Map<String, Object> record = Maps.newHashMap();
            record.put("pickgoodsid", pickGoodsId);
            client.remove(record);
        });
    }

    /**
     * 商品信息变更
     */
    public void changeGoods(Goods goods, EditGoodsDTO edit) {
        // 查询出所有与商品相关的数据更新
        // 变更品牌信息
        if (Check.notEqNullable(goods.getBrandId(), edit.getBrandId())) {
            log.debug("商品信息变更, 更新GoodsId={}有关的优选商品索引", goods.getGoodsId());
            change(page -> searchPickGoodsManager.queryByGoodsId(goods.getGoodsId(), page));
        }
    }

    /**
     * 变更品牌信息
     */
    public void changeBrand(GoodsBrand old, GoodsBrand newBrand) {
        // 变更品牌文案
        if (Check.notEqTrimNullable(old.getName(), newBrand.getName())) {
            log.debug("品牌信息变更, 更新BrandId={}有关的优选商品索引", old.getBrandId());
            change(page -> searchPickGoodsManager.queryByBrandId(old.getBrandId(), page));
        }
    }

    /**
     * 变更分类内容
     */
    public void changeClassify(GoodsClassify old, GoodsClassify newClassify) {
        // 变更分类文案
        if (Check.notEqTrimNullable(old.getName(), newClassify.getName())) {
            if (old.getParentClassifyId() == 0) {
                // 更新父级分类
                log.debug("父级分类信息变更, 更新ParentClassifyId={}有关的优选商品索引", old.getClassifyId());
                change(page -> searchPickGoodsManager.queryByParentClassifyId(old.getClassifyId(), page));
            } else {
                log.debug("分类信息变更, 更新ClassifyId={}有关的优选商品索引", old.getClassifyId());
                change(page -> searchPickGoodsManager.queryByClassifyId(old.getClassifyId(), page));
            }
        }
    }

    /**
     * 变更标签内容
     */
    public void changeLabel(GoodsLabel old, GoodsLabel newLabel) {
        // 变更标签文案
        if (Check.notEqTrimNullable(old.getName(), newLabel.getName())) {
            log.debug("标签信息变更, 更新LabelId={}有关的优选商品索引", old.getLabelId());
            change(page -> pickGoodsLabelAssociateManager.queryByLabelId(old.getLabelId(), page));
        }
    }

    private Map<String, Object> build(Integer pickGoodsId) {
        return build(searchPickGoodsManager.getByPickGoodsId(pickGoodsId));
    }

    private Map<String, Object> build(SearchPickGoodsWithBLOBs record) {
        Map<String, Object> result = Maps.newHashMap();
        result.put("brandid", record.getBrandId());
        result.put("brandname", record.getBrandName());
        result.put("classifyid", record.getClassifyId());
        result.put("classifyname", record.getClassifyName());
        result.put("counterprice", record.getCounterPrice());
        result.put("createtime", record.getCreateTime());
        result.put("createuser", record.getCreateUser());
        result.put("currencytype", record.getCurrencyType());
        result.put("deleted", record.getDeleted());
        result.put("detail", record.getDetail());
        result.put("goodsid", record.getGoodsId());
        result.put("labelidlist", Check.isNullOrTrimEmpty(record.getLabelIdList()) ? StringUtil.EMPTY : record.getLabelIdList().split("[,]"));
        result.put("labelids", record.getLabelIds());
        result.put("labelnamelist", record.getLabelNameList());
        result.put("name", record.getName());
        result.put("parameters", record.getParameters());
        result.put("parentclassifyid", record.getParentClassifyId());
        result.put("parentclassifyname", record.getParentClassifyName());
        result.put("pickgoodsid", record.getPickGoodsId());
        result.put("retailprice", record.getRetailPrice());
        result.put("source", record.getSource());
        result.put("status", record.getStatus());
        result.put("updatetime", record.getUpdateTime());
        result.put("updateuser", record.getUpdateUser());
        return result;
    }

    private void commit(Consumer<DocumentClient> consumer) {
        RunTimingUtil.timing(log, log.isDebugEnabled(), () -> RunningException.wrap(() -> {
            // 黑技术
            DocumentClient documentClient = new DocumentClient(serviceClient) {
                private boolean changeFlag = false;
                @Override
                public void add(Map<String, Object> fields) {
                    super.add(fields);
                    changeFlag = true;
                }

                @Override
                public void update(Map<String, Object> fields) {
                    super.update(fields);
                    changeFlag = true;
                }

                @Override
                public void remove(Map<String, Object> fields) {
                    super.remove(fields);
                    changeFlag = true;
                }

                @Override
                public OpenSearchResult commit(String appName, String tableName) throws OpenSearchException, OpenSearchClientException {
                    if (changeFlag) {
                        return super.commit(appName, tableName);
                    }
                    return null;
                }
            };
            consumer.accept(documentClient);
            documentClient.commit(appName, "search_pick_goods");
        }, "更新优选商品索引异常"), "更新优选商品索引");

    }

    private void change(Function<Page, Pagination> function) {
        Page page = new Page(1, 50);
        do {
            Pagination<?> result = function.apply(page);
            log.debug("查询到总数据 {} 条,待更新数据 {} 条, 分页信息={}", result.getTotalCount(), result.getList().size(), page);
            commit(client -> {
                result.getList()
                        .forEach(record -> {
                            if (record instanceof SearchPickGoodsWithBLOBs) {
                                client.update(build((SearchPickGoodsWithBLOBs) record));
                            } else if (record instanceof PickGoodsLabelAssociate) {
                                client.update(build(((PickGoodsLabelAssociate) record).getPickGoodsId()));
                            }
                        });
            });
            if (result.isLastPage()) {
                break;
            } else {
                page.setPageNo(result.getNextPage());
            }
        } while (true);
        log.debug("批量更新索引结束.");
    }


}
